﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Text;
using System.Web;

namespace SCSInternetBanking.LibertyReserve
{
    public sealed class NvpApiAgent : AbstractApiAgent
    {
        #region Constants

        private class Constants
        {
            public const string True = "true";
            public const string Error = "ERROR";
        }

        private class RequestValueNames
        {
            public const string Id = "Id";
            public const string Search = "Search";
            public const string From = "From";
            public const string Till = "Till";
            public const string Currency = "Currency";
            public const string Direction = "Direction";
            public const string RelatedAccount = "RelatedAccount";
            public const string Reference = "Reference";
            public const string Source = "Source";
            public const string Private = "Private";
            public const string AmountFrom = "AmountFrom";
            public const string AmountTo = "AmountTo";
            public const string Page = "Page";
            public const string Size = "Size";
            public const string Batch = "Batch";
            public const string Type = "Type";
            public const string Payee = "Payee";
            public const string Amount = "Amount";
            public const string Memo = "Memo";
            public const string Purpose = "Purpose";
            public const string Account = "Account";
            public const string Api = "Api";
            public const string Token = "Token";
        }

        private class ResponseValueNames
        {
            public const string HasMore = "HASMORE";
            public const string Count = "COUNT";
            public const string Batch = "BATCH";
            public const string Date = "DATE";
            public const string Payer = "PAYER";
            public const string PayerName = "PAYERNAME";
            public const string Payee = "PAYEE";
            public const string PayeeName = "PAYEENAME";
            public const string Amount = "AMOUNT";
            public const string Fee = "FEE";
            public const string Balance = "BALANCE";
            public const string Currency = "CURRENCY";
            public const string Private = "PRIVATE";
            public const string Source = "SOURCE";
            public const string Memo = "MEMO";
            public const string Reference = "REFERENCE";
            public const string Status = "STATUS";
            public const string ErrorCode = "ERRORCODE";
            public const string ErrorMessage = "ERRORMESSAGE";
            public const string Usd = "USD";
            public const string Euro = "EURO";
            public const string Gold = "GOLD";
        }

        #endregion

        public NvpApiAgent(ApiCredentials credentials)
            : base(credentials)
        {
        }

        #region IApiAgent Members

        public override Balance Balance()
        {
            StringDictionary request = DefaultRequestValues();
            StringDictionary response = SendRequest("/nvp/balance", request);
            return ParseBalanceResponse(response);
        }

        public override string AccountName(string accountNumber)
        {
            StringDictionary request = DefaultRequestValues();
            request.Add(RequestValueNames.Search, accountNumber);
            StringDictionary response = SendRequest("/nvp/accountname", request);
            return ParseAccountNameResponse(response, accountNumber);
        }

        public override HistoryResult History(HistorySpecification specification, int pageIndex, int pageSize)
        {
            StringDictionary request = DefaultRequestValues();
            request.Add(RequestValueNames.From, specification.From.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture));
            request.Add(RequestValueNames.Till, specification.Till.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture));
            request.Add(RequestValueNames.Currency, specification.Currency.ToString());
            request.Add(RequestValueNames.Direction, specification.Direction.ToString());
            request.Add(RequestValueNames.RelatedAccount, specification.RelatedAccount);
            request.Add(RequestValueNames.Reference, specification.Reference);
            request.Add(RequestValueNames.Source, specification.Source.ToString());
            request.Add(RequestValueNames.Private, specification.Private.ToString());
            request.Add(RequestValueNames.AmountFrom, specification.AmountFrom.ToString());
            request.Add(RequestValueNames.AmountTo, specification.AmountTo.ToString());
            request.Add(RequestValueNames.Page, pageIndex.ToString(CultureInfo.InvariantCulture));
            request.Add(RequestValueNames.Size, pageSize.ToString(CultureInfo.InvariantCulture));

            StringDictionary response = SendRequest("/nvp/history", request);
            return new HistoryResult()
            {
                Items = ParseHistoryResponse(response),
                HasMore = HasMorePages(response)
            };
        }

        public override HistoryItem FindTransaction(long batchNumber)
        {
            StringDictionary request = DefaultRequestValues();
            request.Add(RequestValueNames.Batch, batchNumber.ToString(CultureInfo.InvariantCulture));
            StringDictionary response = SendRequest("/nvp/findtransaction", request);
            return ParseFindTransactionResponse(response);
        }

        public override HistoryItem Transfer(Transfer transfer)
        {
            StringDictionary request = DefaultRequestValues();
            request.Add(RequestValueNames.Type, "transfer");
            request.Add(RequestValueNames.Payee, transfer.Payee);
            request.Add(RequestValueNames.Currency, transfer.Currency.ToString());
            request.Add(RequestValueNames.Amount, transfer.Amount.ToString(CultureInfo.InvariantCulture));
            request.Add(RequestValueNames.Memo, transfer.Memo);
            request.Add(RequestValueNames.Reference, transfer.Reference);
            request.Add(RequestValueNames.Private, transfer.Private.ToString());
            request.Add(RequestValueNames.Purpose, transfer.Purpose.ToString());
            StringDictionary response = SendRequest("/nvp/transfer", request);
            return ParseTransferResponse(response);
        }

        #endregion

        private StringDictionary DefaultRequestValues()
        {
            var values = new StringDictionary();
            values.Add(RequestValueNames.Id, DateTime.UtcNow.Ticks.ToString());
            values.Add(RequestValueNames.Account, Credentials.AccountNumber);
            values.Add(RequestValueNames.Api, Credentials.ApiName);
            values.Add(RequestValueNames.Token, Credentials.CreateSecurityToken());
            return values;
        }

        private static Balance ParseBalanceResponse(StringDictionary response)
        {
            CheckError(response);
            return new Balance()
            {
                Usd = decimal.Parse(response[ResponseValueNames.Usd], CultureInfo.InvariantCulture),
                Euro = decimal.Parse(response[ResponseValueNames.Euro], CultureInfo.InvariantCulture),
                Gold = decimal.Parse(response[ResponseValueNames.Gold], CultureInfo.InvariantCulture)
            };
        }

        private static string ParseAccountNameResponse(StringDictionary response, string accountName)
        {
            CheckError(response);
            return response[accountName.ToUpper()];
        }

        private static bool HasMorePages(StringDictionary response)
        {
            return string.Equals(Constants.True, response[ResponseValueNames.HasMore], StringComparison.OrdinalIgnoreCase);
        }

        private static HistoryItem[] ParseHistoryResponse(StringDictionary response)
        {
            CheckError(response);

            List<HistoryItem> list = new List<HistoryItem>();
            int count = Int32.Parse(response[ResponseValueNames.Count], CultureInfo.InvariantCulture);

            for (int index = 1; index <= count; index++)
            {
                list.Add(ParseHistoryItem(response, index.ToString()));
            }

            return list.ToArray();
        }

        private static HistoryItem ParseFindTransactionResponse(StringDictionary response)
        {
            CheckError(response);
            if (!response.ContainsKey(ResponseValueNames.Batch))
            {
                return null;
            }

            return ParseHistoryItem(response, string.Empty);
        }

        private static HistoryItem ParseHistoryItem(StringDictionary response, string index)
        {
            return new HistoryItem()
            {
                Batch = long.Parse(response[ResponseValueNames.Batch + index], CultureInfo.InvariantCulture),
                Date = DateTime.ParseExact(response[ResponseValueNames.Date + index], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
                Payer = response[ResponseValueNames.Payer + index],
                PayerName = response[ResponseValueNames.PayerName + index],
                Payee = response[ResponseValueNames.Payee + index],
                PayeeName = response[ResponseValueNames.PayeeName + index],
                Amount = decimal.Parse(response[ResponseValueNames.Amount + index], CultureInfo.InvariantCulture),
                Fee = decimal.Parse(response[ResponseValueNames.Fee + index], CultureInfo.InvariantCulture),
                Balance = decimal.Parse(response[ResponseValueNames.Balance + index], CultureInfo.InvariantCulture),
                Currency = (Currency)Enum.Parse(typeof(Currency), response[ResponseValueNames.Currency + index], true),
                Private = bool.Parse(response[ResponseValueNames.Private + index]),
                Source = (Source)Enum.Parse(typeof(Source), response[ResponseValueNames.Source + index], true),
                Memo = response[ResponseValueNames.Memo + index],
                Reference = response[ResponseValueNames.Reference + index]
            };
        }

        private static HistoryItem ParseTransferResponse(StringDictionary response)
        {
            CheckError(response);
            return ParseHistoryItem(response, string.Empty);
        }

        private static void CheckError(StringDictionary response)
        {
            if (!response.ContainsKey(ResponseValueNames.Status))
            {
                throw new ApiException("Invalid response format");
            }

            if (string.Equals(Constants.Error, response[ResponseValueNames.Status], StringComparison.OrdinalIgnoreCase))
            {
                string errorCode = response[ResponseValueNames.ErrorCode];
                string errorMessage = response[ResponseValueNames.ErrorMessage];
                throw new ApiException(errorMessage)
                {
                    ErrorCode = Int32.Parse(errorCode, CultureInfo.InvariantCulture)
                };
            }
        }

        private StringDictionary SendRequest(string path, StringDictionary requestData)
        {
            StringBuilder requestBuilder = new StringBuilder();
            foreach (string key in requestData.Keys)
            {
                requestBuilder.Append(string.Concat("&", key, "=", HttpUtility.UrlEncode(requestData[key])));
            }

            string[] response = SendRequest(path, requestBuilder.ToString().Substring(1)).Split('&');
            StringDictionary resultCollection = new StringDictionary();
            foreach (string pair in response)
            {
                string[] tokens = pair.Split('=');
                if (tokens.Length != 2)
                {
                    throw new ApiException("Response structure is invalid");
                }

                resultCollection.Add(tokens[0], HttpUtility.UrlDecode(tokens[1]));
            }

            return resultCollection;
        }
    }
}
